Mise à jour le 26/12/2021
Manipuler les XML en PHP

Manipuler les XML en PHP


💣️Le code ci-dessous a plusieurs années et il n'est pas forcément encore fonctionnel.


1. Itérer à travers les articles d'un flux atom

function getAllFlux($pathFlux)
{
  $document = new DomDocument();
  $document->load($pathFlux);

    $xpath = new DomXPath($document);

    $allFlux = array();
    foreach($xpath->query("//feeds/feed") as $row){
        $keyTmp = $xpath->query("@id",$row)->item(0)->nodeValue;
        $labelTmp = $xpath->query("label",$row)->item(0)->nodeValue;
        $iconTmp = $xpath->query("icon",$row)->item(0)->nodeValue;
        $urlTmp = $xpath->query("url",$row)->item(0)->nodeValue;
        $allFlux[$keyTmp] = array();
        $allFlux[$keyTmp]['label'] = $labelTmp;
        $allFlux[$keyTmp]['url'] = $urlTmp;
        $allFlux[$keyTmp]['iconUrl'] = $iconTmp;
        $allFlux[$keyTmp]['rssUrl'] = $urlTmp;
        $dateHier = gmdate("Y-m-d\TH:i:s\Z", time() - 60 * 60 * 24 * 1);
        $allFlux[$keyTmp]['rssDate'] = $dateHier;
        $allFlux[$keyTmp]['rssUpdateTimeStamp'] = time();
    }
    
    return $allFlux;
}


2. Ajouter un noeud dans un flux atom

function addFlux($pathFlux, $label, $url)
{
    $document = new DomDocument();
    $document->load($pathFlux);
    $xpath = new DomXPath($document);
    $parent = $xpath->query("//opml/body");
    $feed = $document->createElement('outline');
    //$feed->appendChild($document->createElement('label', $label));
    //$feed->appendChild($document->createElement('url', $url));
    //$feed->appendChild($document->createElement('icon', $icon));
    $newnode = $parent->item(0)->appendChild($feed);
    $newnode->setAttribute("text", $label);
    $newnode->setAttribute("title", $label);
    $newnode->setAttribute("type", 'rss');
    $newnode->setAttribute("xmlUrl", $url);
    $newnode->setAttribute("htmlUrl", $url);
    $document->saveXML();
    $document->save($pathFlux);
}


3. Supprimer tous les noeuds d'un flux atom

function removeAllFlux($pathFlux)
{
  $document = new DomDocument();
  $document->load($pathFlux);
  $xpath = new DomXPath($document);

  $node = $xpath->query('/opml/body/outline');
  foreach ($node as $n) {
    $n->parentNode->removeChild($n);
  }
  $document->saveXML();
  $document->save($pathFlux);
}


4. Supprimer un noeud d'un flux atom

function removeFlux($pathFlux, $key)
{
  $document = new DomDocument();
  $document->load($pathFlux);
  $xpath = new DomXPath($document);

  $node = $xpath->query('/opml/body/outline[@title="'.$key.'"]');
  foreach ($node as $n) {
    $n->parentNode->removeChild($n);
  }
  $document->saveXML();
  $document->save($pathFlux);
}


5. Appliquer une feuille XSL à un XML

function transformXMLWithXSL($tpl, $xml, $pParams = array() )
{
  $xslDoc = null;
  $xslFnCache = null;
  $xslt = null;

  // Définir les répertoires contenant les feuilles de style
  $cst = get_defined_constants(true);

  // Le document XML
  $xmlDoc = new DomDocument();
  $rc = $xmlDoc->loadXML($xml);

  if ($rc == false){
  throw new Exception('Loading XML principal document via loadXML()',500);
  }

  if(!is_file($tpl) || !is_readable($tpl)){
  throw new Exception('Feuille XSL absente',500);
  }

  // Création du processor
  $xsl = file_get_contents($tpl, true);

  $xslDoc = new DomDocument;
  $rc = $xslDoc->loadXML($xsl);

  if($rc == false){
  throw new Exception('Loading XSL document via loadXML()',500);
  }

  // L'analyseur XSLT
  $xslt=new XSLTProcessor();

  // Autoriser les appels aux fonctions PHP dans une feuille XSL          
  $xslt->registerPHPFunctions();
  $xslt->importStyleSheet($xslDoc);

  $xslt->setParameter('', $pParams);
  $domHtmlText = $xslt->transformToXML($xmlDoc);

  //Correction d'un bug apparent qui importe des xmlns="" dans les premieres balises des templates
  $domHtmlText =str_replace("xmlns=\"\"", "",$domHtmlText);

  return $domHtmlText;
}


6. Parser un flux atom

<?php
/**
 * Préfixe du XHTML dans les requêtes XPATH
 */
define('XPATH_PREFIX_XHTML', 'x');

/**
 * Namespace XHTML
*/
define('XPATH_NAMESPACE_XHTML', 'http://www.w3.org/1999/xhtml');

/**
 * Préfixe de Atom dans les requêtes XPATH
*/
define('XPATH_PREFIX_ATOM', 'a');

/**
 * Namespace Atom
*/
define('XPATH_NAMESPACE_ATOM', 'http://www.w3.org/2005/Atom');

/**
 * Préfixe de OpenSearch pour les requêtes XPATH
*/
define('XPATH_PREFIX_OPEN_SEARCH', 'openSearch');

/**
 * Namespace OpenSearch
*/
define('XPATH_NAMESPACE_OPEN_SEARCH', 'http://a9.com/-/spec/opensearchrss/1.0/');

/**
 * Namespace XSL
*/
define('XPATH_NAMESPACE_XSL', 'http://www.w3.org/1999/XSL/Transform');

/**
 * Préfixe de Purl pour les requêtes XPATH
 */
define('XPATH_PREFIX_PURL_CONTENT', 'content');

/**
 * Namespace Purl
*/
define('XPATH_NAMESPACE_PURL_CONTENT', 'http://purl.org/rss/1.0/modules/content/');

/*
 * Get a RSS 
 * 
 * @param $ur
 * @return atom
 *  
 */
function getRss($url)
{
    return file_get_contents($url);
}

/*
 * Conversion de xml à tableau associatif de php
* @param $xml   : XML
* @param string $xpath : xpath de element à récupérer
*
* @return array
*/
function convertXmlToTableau($xml,$xpath)
{
    $list = $xml->xpath($xpath);
    $tableau = array();
    foreach ($list as $elt){
        $classArray = array();
        foreach ($elt as $key => $el){
            $value = (string)$el;
            if(empty($classArray[$key])){
                $classArray[$key] = $value;
            }else{
                $classArray[$key] .= ',' . $value;
            }
        }
        $tableau[] = $classArray ;
    }

    return $tableau;
}

function urlExists($url=NULL)  
{  
    if($url == NULL) return false;  
    $ch = curl_init($url);  
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 50);  
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 50);  
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  
    $data = curl_exec($ch);  
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);  
    curl_close($ch);  

    if($httpcode>=200 && $httpcode<300){  
        return true;  
    } else {  
        return false;  
    }  
}

/**
* Return true if the rss is valid, else false 
*/
function is_valid_rss($url){
    if(urlExists($url)){
        return false;
    }
    $content = getRss($url);
    $xmlContent = getSimpleXMLElement($content);
    if($xmlContent !== false){
        define('XPATH_RSS_ITEM', '/rss/channel/item');
        $rssItems = convertXmlToTableau($xmlContent, XPATH_RSS_ITEM);
        $firstItem = reset($rssItems);
        $link = $firstItem['link'];
        $rssTimestamp = strtotime($firstItem['pubDate']);
        if(filter_var($link, FILTER_VALIDATE_URL)  && $rssTimestamp > 0){

            // Return the title
            define('XPATH_RSS_TITLE', '/rss/channel/title');
            $list = $xmlContent->xpath(XPATH_RSS_TITLE);
            return (string)$list[0];
        }
    }
    return false;   
}

/**
 * Fonction de création d'un objet SimpleXMLElement avec enregistrement des
 * espaces de nom à partir d'une chaine de caractères au format XML.
 *
 * @param string $xmlEntree le flux XML permettant de créer le SimpleXMLElement
 * @param string $namespaceParDefaut le namespace par défaut du flux XML (optionnel)
 * @param string $depuisFichier si vrai, alors $xmlEntree est le <strong>chemin ou d'accès</strong> au contenu à tranformer en SXE.
 * @return SimpleXMLElement L'objet SimpleXMLElement dont le contenu est $xmlEntree ou FALSE en cas d'erreur
 */
function getSimpleXMLElement($xmlEntree, $namespaceParDefaut=false, $depuisFichier=false)
{
    $boolDepuisFichier = chaineEnBooleen($depuisFichier);
    // Création de l'objet SimpleXMLElement
    try {
        if($namespaceParDefaut) {
            // un namespace par défaut a été fourni
            $xmlRetour = @(new SimpleXMLElement($xmlEntree, null, $boolDepuisFichier, $namespaceParDefaut, false));
        } else {
            // pas de namespace par défaut
            $xmlRetour = @(new SimpleXMLElement($xmlEntree, null, $boolDepuisFichier));
        }
    } catch (Exception $e) {
        return false;
    }
    // Enregistrement des espaces de noms
    registerDefaultXPathNamespaces($xmlRetour);
    return $xmlRetour;
}

/**
 * Fonction de transformation d'une chaine en booléen
 *
 * Pour PHP, le cast en booléen de la chaine "false" retourne TRUE,
 * ce qui n'est pas le comportement dont nous avons besoin.
 * Cette fonction retourne un booléen
 * - FALSE si le paramètre casté en booléen retourne faux, ou s'il s'agit de la
 *  chaine "false" ou "faux" (insensible à la casse).
 * - TRUE sinon
 * @param string $chaineTest la chaine à transformer
 * @return bool
 */
function chaineEnBooleen($chaineTest)
{
    if( !(bool)$chaineTest
    || !strncasecmp($chaineTest, 'false', 5)
    || !strncasecmp($chaineTest, 'faux', 4) ) {
        // le paramètre est casté en FALSE ou est une chaine "fausse"
        return false;
    } else {
        return true;
    }
}


/**
 * Enregistre la correspondance entre un prefixe et un namespace pour les requêtes XPATH
 * Agit sur les prefixe a (Atom), x (XHTML) et openSearch
 * @param SimpleXMLElement $xml
 */
function registerDefaultXPathNamespaces(SimpleXMLElement $xml)
{
    $xml->registerXPathNamespace(XPATH_PREFIX_ATOM, XPATH_NAMESPACE_ATOM);
    $xml->registerXPathNamespace(XPATH_PREFIX_XHTML, XPATH_NAMESPACE_XHTML);
    $xml->registerXPathNamespace(XPATH_PREFIX_OPEN_SEARCH, XPATH_NAMESPACE_OPEN_SEARCH);
    $xml->registerXPathNamespace(XPATH_PREFIX_PURL_CONTENT, XPATH_NAMESPACE_PURL_CONTENT);
}


function sanitize_output($buffer)
{
    $search = array(
            '/\>[^\S ]+/s', //strip whitespaces after tags, except space
            '/[^\S ]+\</s', //strip whitespaces before tags, except space
            '/(\s)+/s'  // shorten multiple whitespace sequences
    );
    $replace = array(
            '>',
            '<',
            '\\1'
    );
    $buffer = preg_replace($search, $replace, $buffer);

    return $buffer;
}